/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ir, native, HomeCheckIrHint } from 'qvog-dsl';
import { CheckerStorage, CheckerUtils, Defects, Scope, ScopeType } from '../../../Index';
import { ValueType } from '../../../model/NumberValue';
import { Variable } from '../../../model/Variable';
import { VarInfo } from '../../../model/VarInfo';
import { NumberUtils } from '../../../utils/checker/NumberUtils';
import { IssueReport } from '../../../model/Defects';

const SEVERITY = 2;
const DOC_PATH = 'docs/number-init-check-dsl.md';
const DESCRIPTION = 'Number variable of both int and float types detected. The value assigned to a variable should be of the type declared for the variable.';
const visited = new Set();
let issues: IssueReport[] = [];

export default native<HomeCheckIrHint>({ use: 'ir' })()
    .match((stmt): stmt is ir.Stmt => {
        const arkFile = stmt.getCfg()?.getDeclaringMethod().getDeclaringArkFile()
        const file = arkFile.getFilePath();
        if (visited.has(file)) {
            return false;
        } else {
            issues = [];
            visited.add(file);
            check(arkFile);
            return true;
        }
    })
    .report((_, { api }): void => {
        for (const result of issues) {
            api.report({
                location: {
                    file: "",
                    range: {
                        start: {
                            line: result.defect.reportLine,
                            column: result.defect.reportColumn
                        },
                        end: {
                            line: result.defect.reportLine,
                            column: result.defect.reportColumn
                        },
                    }
                },
                severity: SEVERITY,
                description: DESCRIPTION,
                docPath: DOC_PATH,
            })
        }
    });

function check(arkFile: ir.ArkFile): void {
    let filePath = arkFile.getFilePath();
    let scope = CheckerStorage.getInstance().getScope(filePath);
    if (scope) {
        traverseScope(scope);
    }
};

function traverseScope(scope: Scope): void {
    parameteCheck(scope);
    if (scope.childScopeList.length !== 0) {
        for (let childScope of scope.childScopeList) {
            traverseScope(childScope);
        }
    }
}

function parameteCheck(scope: Scope): void {
    if (scope.defList.length === 0) {
        return;
    }
    for (let defValueInfo of scope.defList) {
        let defType: ValueType = ValueType.UNKNOWN;
        let defStmt = defValueInfo.defStmt;
        let defStmtInfo = new VarInfo(defStmt, scope);
        if (defStmt instanceof ir.ArkAssignStmt) {
            let rightOp = defStmt.getRightOp();
            defType = checkValueType(defStmtInfo, rightOp);
            if (defType === ValueType.UNKNOWN) {
                continue;
            }
        }
        checkByDefValueInfo(defValueInfo, defType);
    }
}

function checkValueType(varInfo: VarInfo, value: ir.Value): ValueType {
    const arkFile = varInfo.stmt.getCfg()?.getDeclaringMethod().getDeclaringArkFile();
    if (arkFile) {
        if (!NumberUtils.isValueSupportCalculation(arkFile, varInfo, value)) {
            return ValueType.UNKNOWN;
        }
        let reDefValue = NumberUtils.getNumberByScope(arkFile, varInfo, value);
        return reDefValue.type;
    }
    return ValueType.UNKNOWN;
}

function checkByDefValueInfo(defValueInfo: Variable, defType: ValueType): void {
    let reDefStmtInfos = defValueInfo.redefInfo;
    for (let reDefStmtInfo of reDefStmtInfos) {
        let reDefStmt = reDefStmtInfo.stmt;
        if (reDefStmt instanceof ir.ArkAssignStmt) {
            let rightOp = reDefStmt.getRightOp();
            let reDefType = checkValueType(reDefStmtInfo, rightOp);
            if (reDefType === ValueType.UNKNOWN) {
                break;
            } else if (reDefType !== defType) {
                setIssueReports(reDefStmt);
            }
        }
    }
}

function setIssueReports(reDefStmt: ir.Stmt): void {
    const arkFile = reDefStmt.getCfg()?.getDeclaringMethod().getDeclaringArkFile();
    let originalPosition = reDefStmt.getOriginPositionInfo();
    const lineNo = originalPosition.getLineNo();
    const spacesColumn = originalPosition.getColNo();
    let text = reDefStmt.getOriginalText();
    if (!arkFile || !text || text.length === 0) {
        return;
    }
    const filePath = arkFile.getFilePath();
    const texts = text.split('\n');
    text = texts[0];
    let checkText = '';
    const severity = SEVERITY;
    if (text.includes(';')) {
        if (CheckerUtils.getScopeType(reDefStmt) === ScopeType.FOR_CONDITION_TYPE) {
            checkText = text.substring(text.lastIndexOf(';') + 2, text.indexOf(';'));
            checkText = checkText.substring(checkText.indexOf('=') + 2);
        } else {
            checkText = text.substring(text.indexOf('=') + 2, text.indexOf(';'));
        }
    } else {
        checkText = text.substring(text.indexOf('=') + 2);
    }
    const checkTextLen = checkText.length;
    const startColumn = spacesColumn + text.indexOf(checkText);
    const endColumn = startColumn + checkTextLen - 1;
    let defects = new Defects(lineNo, startColumn, endColumn, DESCRIPTION, severity,
        "", filePath, DOC_PATH, true, false, false);
    issues.push(new IssueReport(defects, undefined));
}